/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
/**
 * Created on 24.10.2004
 *
 */
package robocup.component;

import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.ControllerNumbers.VIEW_ANGLE;
import robocup.component.actions.Action;
import robocup.component.actions.AttentionToAction;
import robocup.component.actions.ChangeViewModeAction;
import robocup.component.actions.PointToAction;
import robocup.component.actions.SayAction;
import robocup.component.actions.TurnNeckAction;
import robocup.component.actions.Action.TYPE;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.SenseBodyInfo;
import robocup.component.infotypes.VisualInfo;
import robocup.component.math.RCMath;
import robocup.connection.TeamSoccerScopeMediator;

/**
 * This class synchronizes the client with the server.
 * 
 * As we are using a viewmode logic of 2x "narrow (45 deg.)" followed by 
 * 1x "medium (90 deg.)" the visual infos (VIs) should arrive in steps of 75ms,
 * 75ms and 150ms(mdeium). As for the better quality we would like to have a 
 * visual info each cycle (100ms) before determining an action. Therefore we 
 * will change the viewmode to "medium" only if the last VI arrived in the 
 * first quarter of an cycle and "narrow" when in the 2nd or 3rd quarter.
 * (We could easily achive the goal of 1 VI each cycle by using only the
 * "narrow" viewmode, but as this would result in 4 VIs in 3 Cycles and as the 
 * 2nd VI in a cycle contains no new information we would loose 1 VI or
 * ,in other words, we would end up with 45 degrees less seen in 300ms.) 
 * 
 * Due to noise, network latency and the delay for updating the world model and 
 * calculating the corresponding action a VI arrival in the 4th quarter is 
 * not diserable.
 * 
 * A perfect frequenzy would therefor look like this:
 * 
 * VI Arrival Quarter:  1234 1234 1234 1234 1234 1234 1234 1234 1234 1234 ....
 * New Viewmode      :  m      n   n   m      n   n   m      n   n   m    .... 
 *                      (m=medium, n=narrow)
 * 
 * The client always tries to wait for the VI before calculating an action.
 * However, due to fact that the calculation of some actions (e.g. passing,
 * scoring, dribbling,..) might take some time. To avoid the resulting holes
 * we calculate a preliminary (and lower quality) action based only on the 
 * body sense info (BS-info). If not overwritten by a better action based on a 
 * VI this action is send to the server a short time before the end of a cycle.
 * As this problem nearly only occurs for late VI, a preliminary action is 
 * only calculated when we expect the VI to arrive in the 3rd quarter thus
 * saving calculating capacity that may be used by other players. The rate of 
 * sending based only on a BS-Info normally remains below 1%.
 * 
 * @author Maximilian Kern, Robert Wetzker
 * 
 * TODO
 * 
 *  - include wide view mode (180 degrees)
 *  - use viewmode (narrow + quality low) for moving the VI arrival offset 
 *    by +-12.5 ms. 
 *    (the next VI should arrive after 37.5 ms. 37.5 mod 25 = 12.5ms)
 *     
 *    i.e. 
 *    
 *    VI-Arrivals: 20ms -> medium (+150ms) -> 70ms ->  narrow (+75ms)-> 45ms
 *                   -> narrow -> 20ms ....
 *    
 *    With Correction:
 *    
 *    20ms -> narrow + low (+37.5ms) -> 57.5ms -> medium -> 7.5ms -> medium -> 
 *      57.5ms -> narrow -> 32.5ms -> narrow -> 7.5ms ...
 *      
 *    The max offset changing from 70ms to 57.5ms adding 12.5 ms for better 
 *    action calculation.
 *    
 *    (HAS TO BE TESTED YET!!!)
 *    
 *    
 */
public class Synchro {

	/**
	 * Info statistics.
	 * 
   * This statistics show the following:
   * 
   * Number of VIs
   * Number of late VIs (VIs that arrived after the sending)
   * Ratio of late VIs
   * Number of 2nd VIs  (VIs that arrive in a cycle where already arrived a VI)
   * Ratio of 2nd VIs
   * Ratio of VIs with view mode = narrow
   * Ratio of VIs with view mode = medium
   * Ratio of actions sent based on a BS-info
   * Number of Cycles without a VI arrival
   * Ratio of Cycles without a VI arrival
   * Average length of a cycle
   * Variance of the cylce length
   * Average time needed for calculating an action
   * Variance time needed for calculating an action
   * 
	 * @author Robert Wetzker 
	 */
	private class InfoStatistic {
		// number VIs
		private int viCounter = 0;

		// number BS
		private int sbCounter = 0;

		// accumulated time between BS
		private long accumSBTime = 0;

		// variance of sb_length
		private double varSBLength = 0;

		// VIs that arrived after sending
		private int lateVIs = 0;

		// VIs that are not the first VI of the cycle
		private int secondVIs = 0;

		// cycles without VIs
		private int emptyCycles = 0;

		// cycles with viewmode = 75ms
		private int vi75 = 0;

		// cycles with viewmode = 150ms
		private int vi150 = 0;

		// time between vi_arrival and sending
		private long actionEffort = 0;

		// time between vi_arrival and sending
		private long actionEffortCounter = 0;

		// var of action effort
		private double varActionEffort = 0;

		//number of actions sent based on a VI
		private int sentAfterVI = 0;

    //number of actions sent based on a BS
		private int sentAfterBS = 0;

    //the synchro bean
		private Synchro synchro;

    /**
     * The Constructor.
     * 
     * @param synchro - the synchro bean
     */
		private InfoStatistic(Synchro synchro) {
			this.synchro = synchro;
		}

		/**
		 * update statistic after an VI arrival
		 * 
		 * @param info - VI
		 */
		private void update(VisualInfo info) {
			// as the Server sends sometimes strange things during the
			// init period only Infos that arrive during the game are considered
			if (info.getCycle() == 0)
				return;
			this.viCounter++;
			if (this.synchro.is2ndVI)
				this.secondVIs++;
			else if (this.synchro.wasSent())
				this.lateVIs++;
		}

		/**
		 * update statistic after BS info arrival.
		 * 
		 * @param info - BS info
		 */
		private void update(SenseBodyInfo info) {
			// as the Server sends quiet strange things during the
			// init period only Info that arrive during the game are considered
			if (info.getCycle() == 0)
				return;
			
      this.sbCounter++;
			
      if (getWorld().getLongTermMemory().getVIArrivalTimes().length() > 0
					&& getWorld().getLongTermMemory().getBSArrivalTimes()
							.length() > 0){
        
        //no VI arrived in last cycle
				if (getWorld().getLongTermMemory().getLastVIArrivalTime() < getWorld()
						.getLongTermMemory().getLastBSArrivalTime()) {
					this.emptyCycles++;
				}
      }
      
			if (info.getViewWidth() == VIEW_ANGLE.NARROW)
				this.vi75++;
			
      if (info.getViewWidth() == VIEW_ANGLE.NORMAL)
				this.vi150++;
			
      
      //remeber cycle length + var
      if (getWorld().getLongTermMemory().getBSArrivalTimes().length() > 0
					&& this.sbCounter > 1) {
				this.accumSBTime += this.synchro.getLastCycleLength();
				this.varSBLength = (double) (this.varSBLength
						* (this.sbCounter - 1) + Math
						.pow(
								this.synchro.getLastCycleLength()
										- (double) (this.accumSBTime / (this.sbCounter - 1)),
								2))
						/ this.sbCounter;
			}
		}

    /**
     * updates the statistics after the sending of an action.
     * 
     * determines the time needed for the action calculation.
     *
     */
		public void updateAfterSending() {
			long lastArrivalTime;
			if (getWorld().getLongTermMemory().getVIArrivalTimes().length() > 0
					&& getWorld().getLongTermMemory().getBSArrivalTimes()
							.length() > 0){
				if (getWorld().getLongTermMemory().getLastVIArrivalTime() > getWorld()
						.getLongTermMemory().getLastBSArrivalTime()) {
					lastArrivalTime = getWorld().getLongTermMemory()
							.getLastVIArrivalTime();
					this.actionEffort += (System.nanoTime() - lastArrivalTime) / 1000000;
					this.actionEffortCounter++;
					this.varActionEffort = (double) (this.varActionEffort
							* (this.actionEffortCounter - 1) + Math
							.pow(
									(System.nanoTime() - lastArrivalTime)
											/ 1000000
											- (double) (this.actionEffort / this.actionEffortCounter),
									2))
							/ this.actionEffortCounter;
				}
      }
      
			if (getWorld().isViBased()) {
				this.sentAfterVI++;
			} else {
				this.sentAfterBS++;
			}
		}

    /**
     * resets the values of the statistics
     */
		@SuppressWarnings("unused")
		private void reset() {
			this.viCounter = 0;
			this.sbCounter = 0;
			this.lateVIs = 0;
			this.secondVIs = 0;
			this.emptyCycles = 0;
			this.accumSBTime = 0;
			this.varSBLength = 0;
			this.actionEffort = 0;
			this.actionEffortCounter = 0;
			this.varActionEffort = 0;
		}

    /**
     * prints the statistic.
     *
     */
		private void show() {
			System.out.println("C"
					+ getWorld().getCycle()
					+ " P"
					+ getWorld().getNumber()
					+ " VI_Count:"
					+ this.viCounter
					+ "\t late VIs:"
					+ this.lateVIs
					+ " %:"
					+ RCMath.round2((double) this.lateVIs / this.viCounter * 100)
					+ "\t 2nd VIs:"
					+ this.secondVIs
					+ " %:"
					+ RCMath.round2((double) this.secondVIs / this.viCounter * 100)
					+ "\t 75ms%:"
					+ RCMath.round2((double) this.vi75 / this.sbCounter * 100)
					+ "\t 150ms%:"
					+ RCMath.round2((double) this.vi150 / this.sbCounter * 100)
					+ "\t BS%:"
					+ RCMath.round2((double) this.sentAfterBS
							/ (this.sentAfterBS + this.sentAfterVI) * 100)
					+ "\t Empty:"
					+ this.emptyCycles
					+ " %:"
					+ RCMath.round2((double) this.emptyCycles / this.sbCounter * 100)
					+ "\t avg_cycle_length:"
					+ RCMath.round2((double) this.accumSBTime / (this.sbCounter - 1))
					+ "\t var:"
					+ RCMath.round2(this.varSBLength)
					+ "\t time/action:"
					+ RCMath.round2((double) this.actionEffort
							/ this.actionEffortCounter) + "\t var:"
					+ RCMath.round2(this.varActionEffort));
		}
	}

	/**
	 * Hole statistics.
   * 
   * statistic for hole measurement. used also for determing which of the sent
	 * actions were considered by the SServer. A hole is defined as a sent action 
   * that wasn't considered by the SoccerServer.
   * 
   * This statistics show the following:
   * 
   * main action holes
   * main action holes (for cycles where the player has the ball)
   * turn neck action holes
   * change view mode action holes
   * cycles where no action was sent
   * Ratio of main actions calculated but not send (i.e. (turn 0.0))
   * 
	 */
	private class HoleStatistic {
    
		// hole in last cycle (main action)
		private boolean hole_main = false;

    // hole in last cycle (turn neck action)
		private boolean hole_neck = false;

    // hole in last cycle (change view mode action)
		private boolean hole_view = false;

		// hole counter
		private int holes_main = 0;
		
    // hole neck
		private int holes_neck = 0;

    // holes view mode change
		private int holes_view = 0;

    // holes when with ball
		private int holes_with_ball = 0;

		// cycles were no actions were sent
		private int unsent = 0;

		// main actions that were not sent because they wouldn't effect the
		// world
		private int filteredMains = 0;

		// action counter (main)
		private int count_main = 0;

    // action counter (neck)
		private int count_neck = 0;

    // action counter (view)
		private int count_view = 0;

    // action counter (main with ball)
		private int count_with_ball = 0;

		// number of cycles
		private int cycles = 0;

    /**
     * Constructor.
     *
     */
		private HoleStatistic() {
		}

		/**
		 * This method has to be called each time a SB arrives.
		 * 
		 * @param sbInfo
		 * @param synchro
		 */
		private void update(SenseBodyInfo sbInfo, Synchro synchro) {
			if (sbInfo.getCycle() == 0)
				return;

			// reset
			this.hole_main = false;
			this.hole_neck = false;
			this.hole_view = false;

			this.cycles = sbInfo.getCycle();

			if (!synchro.wasSent()) {
        //nothing was sent
				this.unsent++;
				this.hole_main = true;
				this.hole_neck = true;
				this.hole_view = true;
			} 
      else {
        
        //main action was filtered
				if (synchro.mainAction == null) {
					// world has not been updated yet
					if (getWorld().canKick()
							&& getWorld().getPlayMode() == PLAY_MODE.PLAY_ON) {
						this.holes_with_ball++;
					}
					this.filteredMains++;
				}

				// main
				if (sbInfo.getMainActionsCountSum() == this.count_main
						&& synchro.mainAction != null) {
					// System.out.println(cycles + "HOLE P:" +
					// synchro.parent.getNumber() + "-" + mainAction);
					this.hole_main = true;
					this.holes_main++;

					// world has not been updated yet
					if (getWorld().canKick()) {
						this.holes_with_ball++;
					}
				}
        
				// neck
				if (sbInfo.getCount(TYPE.TURN_NECK) == this.count_neck
						&& synchro.turnNeckAction != null) {
					this.hole_neck = true;
					this.holes_neck++;
				}
				// changeViewMode
				if (sbInfo.getCount(TYPE.CHANGE_VIEW_MODE) == this.count_view
						&& synchro.viewModeAction != null) {
					this.hole_view = true;
					this.holes_view++;
				}

				// update action counters

				// world has not been updated yet
				if (getWorld().canKick()) {
					this.count_with_ball++;
				}

				this.count_main = sbInfo.getMainActionsCountSum();
				this.count_neck = sbInfo.getCount(TYPE.TURN_NECK);
				this.count_view = sbInfo.getCount(TYPE.CHANGE_VIEW_MODE);
			}
		}

    /**
     * prints the statistic.
     *
     */
		private void show() {
			System.out
					.println("HOLES C"
							+ this.cycles
							+ " P:"
							+ Synchro.this.parent.getNumber()
							+ "\t MAIN:"
							+ this.holes_main
							+ " %:"
							+ ((this.count_view + this.holes_view == 0) ? -1
									: RCMath.round2((double) this.holes_main
											/ (this.count_main + this.holes_main)
											* 100))
							+ "\t WITH_BALL:"
							+ this.holes_with_ball
							+ " %:"
							+ ((this.count_with_ball + this.holes_with_ball == 0) ? -1
									: RCMath.round2((double) this.holes_with_ball
											/ (this.count_with_ball + this.holes_with_ball)
											* 100))
							+ "\t NECK:"
							+ this.holes_neck
							+ " %:"
							+ ((this.count_view + this.holes_view == 0) ? -1
									: RCMath.round2((double) this.holes_neck
											/ (this.count_neck + this.holes_neck)
											* 100))
							+ "\t VIEW:"
							+ this.holes_view
							+ " %:"
							+ ((this.count_view + this.holes_view == 0) ? -1
									: RCMath.round2((double) this.holes_view
											/ (this.count_view + this.holes_view)
											* 100))
							+ "\t UNSENT:"
							+ this.unsent
							+ " %:"
							+ RCMath.round2((double) this.unsent / (cycles) * 100)
							+ "\t FILTERED:"
							+ this.filteredMains
							+ " %:"
							+ RCMath.round2((double) this.filteredMains / (cycles)
									* 100));
		}
	}

	// the parent
	private RobocupAgent parent;

	// arrival time in ns of current SenseBodyInfo
	private long sbArrival = -2;

	// the actions are stored here before sending.
	private Action mainAction = null;

	private TurnNeckAction turnNeckAction = null;

	private ChangeViewModeAction viewModeAction = null;

	private AttentionToAction attention = null;

	private PointToAction pointTo = null;

	private SayAction sayAction = null;

	// actions were already sent this cycle
	private boolean sent = false;

	// arrivaltime_vi - arrivaltime_bs
	private double lastVIArrivalOffset = -1;

	// the arrival quarter of the last VI (in an ideal world: 0 -> 0-25ms, 1 ->
	// 25-50ms .....)
	private int viArrivalQuarter = 3;

	// the expected arrival quarter of the next VI (in an ideal world: 0 ->
	// 0-25ms, 1 -> 25-50ms .....)
	private int nextVIArrivalQuarter = 3;

	// cycle length of last info (not used yet) rw
	private long lastCycleLength = 100;

  // the VI is the second VI in this cycle
	private boolean is2ndVI = false;

	// no VI arrived in the last cycle
	private boolean lastCycleWasEmpty = false;

	// action should be calculated after a BS because we don't expect a VI
	// or the VI arrival is unsure
	private boolean calculateBSAction = true;

	// the timer for the sending after an BS
	Timer bsTimer = new Timer();

	// Statistics
	private InfoStatistic infoStatistic = new InfoStatistic(this);

	private HoleStatistic holeStatistic = new HoleStatistic();

	// print the Info-statistic
	private boolean SHOW_STATISTIC = false;

	// the actions that will be send
	StringBuffer actions = new StringBuffer();

  // change view mode action was sent
	private boolean viewModeSent;

	/**
	 * create a new synchro - this object should predict the times, when it is
	 * necessary to send actions
	 * 
	 * @param parent -
	 *            the agent object
	 */
	public Synchro(RobocupAgent parent) {
		this.parent = parent;
	}

	/**
	 * This is called every new cycle resetting all actions
   * and some flags.
	 */
	private void reset() {

		this.mainAction = null;
		this.turnNeckAction = null;
		this.pointTo = null;
		this.viewModeAction = null;
		this.sayAction = null;
		this.attention = null;
		this.sent = false;
    this.viewModeSent = false;

		this.is2ndVI = false;
	}

	/**
	 * Called out of PlayerBean when a body sense info was parsed
	 */
	public synchronized void arrivedBS(SenseBodyInfo info) {
		
    // cancel timer -> no more sending from last cycle
		this.bsTimer.cancel();


		// update hole statistic
		this.holeStatistic.update(info, this);
    
		// update gui hole statistic
		if (this.holeStatistic.hole_main || this.holeStatistic.hole_neck
				|| this.holeStatistic.hole_view) {
			if (this.parent.useGui())
				TeamSoccerScopeMediator.registerHole(this.parent, 1);
		}

		// set flag if no vi arrived in last cycle
		if (getWorld().getLongTermMemory().getVIArrivalTimes().length() > 0
				&& getWorld().getLongTermMemory().getBSArrivalTimes().length() > 0
				&& getWorld().getLongTermMemory().getLastVIArrivalTime() < getWorld()
						.getLongTermMemory().getLastBSArrivalTime()) {
			this.lastCycleWasEmpty = true;
		} else {
			this.lastCycleWasEmpty = false;
		}

    //calculate what quarter the last VI was in
		if (this.getWorld().getLongTermMemory().getBSArrivalTimes().length() > 0) {
			this.lastCycleLength = (info.getArrivalTime() - this.sbArrival) / 1000000;
			if (this.getWorld().getLongTermMemory().getVIArrivalTimes()
					.length() > 0) {
				// 75 or 150ms? 
				double interval = info.getViewQuality().getFactor()
						* info.getViewWidth().getFactor() * 150; 
				
				int shift = (short) interval / 25; 

				this.nextVIArrivalQuarter = this.viArrivalQuarter + shift - 4;

				// if the VI is expected in the second cycle half and we have the ball
				// we calculate actions after BS for security reasons
				if (this.nextVIArrivalQuarter > 1 && this.getWorld().canKick()) {
					this.calculateBSAction = true;
				} else {
					this.calculateBSAction = false;
				}
			}
			this.viArrivalQuarter = this.nextVIArrivalQuarter;

		}

		// sent actions
    // the actions that were sent and accepted by the SServer will be
    // used to update the WorldModel after an BS info
		Vector<Action> lastSentActions = new Vector<Action>();
		if (!this.holeStatistic.hole_main && this.mainAction != null)
			lastSentActions.add(this.mainAction);
		if (!this.holeStatistic.hole_neck && this.turnNeckAction != null)
			lastSentActions.add(this.turnNeckAction);
		if (!this.holeStatistic.hole_view && this.viewModeAction != null)
			lastSentActions.add(this.viewModeAction);
		if (this.attention != null)
			lastSentActions.add(this.attention);
		if (this.sayAction != null)
			lastSentActions.add(this.sayAction);
		if (this.pointTo != null)
			lastSentActions.add(this.pointTo);

		info.setLastSentActions(lastSentActions);

		// missed actions are remembered as well (not used yet)
		Vector<Action> lastMissedActions = new Vector<Action>();

		if (this.holeStatistic.hole_main)
			lastMissedActions.add(this.mainAction);
		if (this.holeStatistic.hole_neck)
			lastMissedActions.add(this.turnNeckAction);
		if (this.holeStatistic.hole_view)
			lastMissedActions.add(this.viewModeAction);

		info.setLastMissedActions(lastMissedActions);

		// set actions and flags to "null"
		reset();

		this.sbArrival = info.getArrivalTime();

		// statistics
		if (this.SHOW_STATISTIC)
			this.infoStatistic.update(info);
		if (this.SHOW_STATISTIC && getWorld().getCycle() % 1000 == 999) {
			this.infoStatistic.show();
			this.holeStatistic.show();
		}
	}

	/**
	 * Called out of PlayerBean when a visual info arrives
	 */
	public synchronized void arrivedVI(VisualInfo visualInfo) {
		// cancel Timer
		this.bsTimer.cancel();

		// vi_arrival - bs_arrival
		if (this.getWorld().getLongTermMemory().getBSArrivalTimes().length() == 0)
			this.lastVIArrivalOffset = 80;
		else
			this.lastVIArrivalOffset = (visualInfo.getArrivalTime() - this
					.getWorld().getLongTermMemory().getLastBSArrivalTime()) / 1000000;

		// is it the second VI that arrives in this cycle
		this.is2ndVI = (this.getWorld().getLongTermMemory().getVIArrivalTimes()
				.length() > 1) ? this.getWorld().getLongTermMemory()
				.getLastVIArrivalTime() > this.sbArrival : false;

		// no vi in last cycle -> this vi arrived at the beginning
		// TODO consider 300ms intervals
		if (this.lastCycleWasEmpty)
			this.viArrivalQuarter = 0;
		// vi arrived n 1st quarter of cycle
		if (this.lastVIArrivalOffset < 25)
			this.viArrivalQuarter = 0;
		// vi arrived in last quarter of cycle
		// VALID only if no 37.5ms modes are used!!!
		if (this.is2ndVI)
			this.viArrivalQuarter = 3;

		// statistics
		if (this.SHOW_STATISTIC)
			this.infoStatistic.update(visualInfo);
	}

	/**
	 * Called out of PlayerBean when a aural info arrived
	 */
	public synchronized void arrivedAural(AuralInfo auralInfo) {
	}

	/**
	 * send actions. if actions are based on a VI this method sends these
	 * actions to the SoccerServer (via RobocupAgent). If based on BS a
	 * TimerTask is started which will send the actions if no VI arrives.
	 * 
	 */
	public synchronized void send() {

    //we got an action but it was based only on an BS info
    // so we wait as long as possible hoping a VI might arrive improving our worldmodel
    // and leading to a better action.
    
    //the time we will wait is based on the expected duration of the cycle (might differ
    // due to slow server and/or network delay) minus the expected time for calculating an action
    // in the case a VI will arrive minus some threshold.
		if (this.calculateBSAction == true
				&& !this.getWorld().isViBased()) {
      
			long minCycleLength;
			if (this.getWorld().getLongTermMemory().getCycleLength().length() > 0)
				minCycleLength = this.getWorld().getLongTermMemory()
						.getCycleLength().getMin(5);
			else
				minCycleLength = SConf.getInstance().SENSE_BODY_STEP * 1000000;

			long thinkTime = 5000000;
      
      //action calculation with the ball kickable is more time consuming
			if (this.getWorld().canKick()) {
				if (this.getWorld().getLongTermMemory().getThinkTimesWithBall()
						.length() > 0) {
					thinkTime = this.getWorld().getLongTermMemory()
							.getThinkTimesWithBall().getMax(5);
				}
			} else {
				if (this.getWorld().getLongTermMemory().getThinkTimes45()
						.length() > 0) {
					thinkTime = this.getWorld().getLongTermMemory()
							.getThinkTimes45().getMax(5);
				}
			}

      //world update and action calculation with more seen objects
      // and therefor more information to consider might be more time consuming
			if (this.getWorld().getViewWidth() == 90.0) {
				thinkTime *= 1.5;
			}

			long delay = (long) (minCycleLength - thinkTime + this.sbArrival - System
					.nanoTime()) / 1000000 - 20;

			if (delay < 0)
				this.sendAllButTurnNeckAndAttention();
			else
				this.setTimer(delay);
		}
		// based on VI -> sendAll
		else {
			this.sendAll();
		}
	}

	/**
	 * Sends all actions to be executed and stores some
   * meta data.
   * 
   * All sent actions are remembered.
	 */
	private synchronized void sendAll() {
		if (this.sent) {
			return;
		}

		// save thinking times
		if (this.getWorld().isViBased()
				&& this.getWorld().getLongTermMemory().getVIArrivalTimes()
						.length() > 0) {
			long thinkTime = System.nanoTime()
					- this.getWorld().getLongTermMemory().getVIArrivalTimes()
							.getLast();
			double viewWidth = this.getWorld().getViewWidth();
			if (viewWidth == 45.0) {
				if (this.getWorld().canKick()) {
					this.getWorld().getLongTermMemory().getThinkTimesWithBall()
							.insert(thinkTime);
				} else {
					this.getWorld().getLongTermMemory().getThinkTimes45()
							.insert(thinkTime);
				}
			}
		}

		this.actions.setLength(0);

		// main
		if (this.mainAction == null) {

		} else {
			if (this.mainAction.isWorthSending())
				this.actions.append(this.mainAction.toString());
			else
				this.mainAction = null;
		}

		// turnNeck
		if (this.turnNeckAction == null) {

		} else {
			if (this.turnNeckAction.isWorthSending())
				this.actions.append(this.turnNeckAction);
			else
				this.turnNeckAction = null;
		}

		// viewMode
		if (this.viewModeAction == null) {

		} else if (!this.viewModeSent) {
			// TODO nicht senden wenn bereits vorhanden
			this.actions.append(this.viewModeAction);
		}

		// say
		if (this.sayAction == null) {

		} else {
			// TODO nicht senden wenn bereits vorhanden
			this.actions.append(this.sayAction);
		}

		// pointTo
		if (this.pointTo == null) {

		} else {
			// TODO nicht senden wenn bereits vorhanden
			this.actions.append(this.pointTo);
		}

		// attention to
		if (this.attention == null) {

		} else {
			// TODO nicht senden wenn bereits vorhanden
			this.actions.append(this.attention);
		}

		if (this.actions.length() != 0) {
			// here everything is sent
			this.parent.sendActions(this.actions.toString());
		}
		this.sent = true;

		if (this.SHOW_STATISTIC)
			this.infoStatistic.updateAfterSending();
	}

	/**
	 * Send all actions but the turn neck and the attentionto action to the SServer.
   * 
   * This is used for sending based on a BS info where the last sent turn neck action
   * still had no effect on our world vision and the attentionTo action had no effect
   * as hear messages arrive after the BS info.
	 */
	private synchronized void sendAllButTurnNeckAndAttention() {

		// save thinking times
		if (this.getWorld().isViBased()
				&& this.getWorld().getLongTermMemory().getVIArrivalTimes()
						.length() > 0) {
			long thinkTime = System.nanoTime()
					- this.getWorld().getLongTermMemory().getVIArrivalTimes()
							.getLast();
			double viewWidth = this.getWorld().getViewWidth();
			if (viewWidth == 45.0) {
				if (this.getWorld().canKick()) {
					this.getWorld().getLongTermMemory().getThinkTimesWithBall()
							.insert(thinkTime);
				} else {
					this.getWorld().getLongTermMemory().getThinkTimes45()
							.insert(thinkTime);
				}
			}
		}

		this.actions.setLength(0);

		// main
		if (this.mainAction == null) {

		} else {
			if (this.mainAction.isWorthSending())
				this.actions.append(this.mainAction.toString());
			else
				this.mainAction = null;
		}

		// viewMode
		if (this.viewModeAction == null) {

		} else {
			// TODO nicht senden wenn bereits vorhanden
			this.actions.append(this.viewModeAction);
		}

		// say
		if (this.sayAction == null) {
			if (false)
				System.out.println(this.parent.getNumber() + " P"
						+ this.getWorld().getNumber()
						+ " Es wurde keine SayAction bestimmt.");
		} else {
			// TODO nicht senden wenn bereits vorhanden
			this.actions.append(this.sayAction);
		}

		if (this.actions.length() != 0) {
			// here everything is sent
			this.parent.sendActions(this.actions.toString());
		}
		this.sent = true;

		if (this.SHOW_STATISTIC)
			this.infoStatistic.updateAfterSending();
	}

	/**
	 * the parent (PlayerBean)
	 * 
	 * @return
	 */
	@SuppressWarnings("unused")
	private RobocupAgent getParent() {
		return this.parent;
	}

	/**
	 * true if actions were sent in this cycle.
	 * 
	 * @return
	 */
	public boolean wasSent() {
		return this.sent;
	}

	/**
	 * @return Returns the lastVIArrivalOffset.
	 */
	public double getLastVIArrivalOffset() {
		return this.lastVIArrivalOffset;
	}

	/**
	 * @return Returns the lastCycleLength.
	 */
	public long getLastCycleLength() {
		return this.lastCycleLength;
	}

	/**
	 * @return Returns the viArrivalQuarter.
	 */
	public int getVIArrivalQuarter() {
		return this.viArrivalQuarter;
	}

	/**
	 * the percentage of holes for cycles where the player can kick the ball
	 * 
	 * @return
	 */
	public double getWithBallHolePercentage() {
		if (this.holeStatistic.count_with_ball == 0)
			return 0;

		return (double) this.holeStatistic.holes_with_ball
				/ this.holeStatistic.count_with_ball;
	}

	/**
	 * @return Returns the is2ndVI.
	 */
	public boolean is2ndVI() {
		return this.is2ndVI;
	}

	/**
	 * @return Returns the calculateBSAction.
	 */
	public boolean calculateBSAction() {
		return this.calculateBSAction;
	}

	/**
	 * Sets the TimerTask after the arrival of a BS.
	 * 
	 * @param offset
	 */
	public void setTimer(long offset) {
		this.bsTimer = new Timer();
		this.bsTimer.schedule(invokeBSCycle(this), offset);
	}

	/**
	 * simple TimerTask that sends after a certain waiting period.
	 * 
	 * @param synchro
	 * @return
	 */
	private static TimerTask invokeBSCycle(final Synchro synchro) {
		return new TimerTask() {

			@Override
			public void run() {
				synchro.sendAllButTurnNeckAndAttention();
			}
		};
	}

	/**
	 * The WorldModel.
	 * 
	 * @return the WM.
	 */
	private WorldModel getWorld() {
		return this.parent.getWorld();
	}

	/**
	 * @param mainAction
	 *            The mainAction to set.
	 */
	public void setMainAction(Action mainAction) {
		this.mainAction = mainAction;
	}

	/**
	 * @param turnNeckAction
	 *            The turnNeckAction to set.
	 */
	public void setTurnNeckAction(TurnNeckAction turnNeckAction) {
		this.turnNeckAction = turnNeckAction;
	}

	/**
	 * @param viewModeAction
	 *            The viewModeAction to set.
	 */
	public void setViewModeAction(ChangeViewModeAction viewModeAction) {
		if (getWorld().isViBased())
			this.viewModeAction = viewModeAction;
		else
			this.viewModeAction = null;
	}

	/**
	 * This Method adds a say-Message to the queue
	 * 
	 * @param say
	 */
	public void setSayAction(SayAction say) {
		this.sayAction = say;
	}

	/**
	 * This method sets the attention-action
	 * 
	 * @param att
	 */
	public void setAttentionToAction(AttentionToAction att) {
		this.attention = att;
	}

	/**
	 * This method sets the pointTo-Action!
	 */
	public void setPointToAction(PointToAction pta) {
		this.pointTo = pta;
	}

	/**
	 * @return Returns the mainAction.
	 */
	public Action getMainAction() {

		return this.mainAction;
	}

	/**
	 * @return Returns the viewModeAction.
	 */
	public ChangeViewModeAction getViewModeAction() {

		return this.viewModeAction;
	}
}
